Debugging a Multi-Module Scala Project: A Personal Journey Through Version Conflicts and Build Complexities

Debugging a Multi-Module Scala Project: A Personal Journey Through Version Conflicts and Build Complexities

The Beginning: When Nothing Compiles

I started with what seemed like a straightforward task — implementing functional programming concepts in Scala. The requirements were clear: create functions for trigonometric calculations, implement currying and partial application, work with collections using anonymous functions, and build higher-order functions. What I didn’t expect was to spend time debugging build issues that had nothing to do with my actual implementations.

Discovery: The First Signs of Trouble

My initial attempt to compile the project from within the core module revealed the first problem:

cd core && sbt compile

The output immediately showed a compilation error that made my heart sink:

[error] object Using is not a member of package util
[error] import scala.util.Using

This error was particularly puzzling because scala.util.Using is a standard utility for resource management in Scala. Why wasn't it available?

Investigation: Peeling Back the Layers

I needed to understand what was happening beneath the surface. My first instinct was to check which Scala version the project was actually using:

grep -i "scalaVersion" build.sbt

This revealed: ThisBuild / scalaVersion := "2.13.16"

The build configuration clearly specified Scala 2.13.16, yet I was getting errors suggesting an older version. To dig deeper, I checked what SBT actually reported:

sbt "show scalaVersion"

This command confirmed all modules were configured for Scala 2.13.16. The mystery deepened - if the configuration said 2.13, why was I getting 2.12-specific errors?

Root Cause Analysis: Three Interconnected Problems

Problem 1: The Context Trap

The revelation came when I examined the compilation output more carefully. When running SBT from within the core subdirectory, it was creating an isolated project context with default settings, ignoring the parent project's configuration. The default Scala version on my system was 2.12.18, explaining the mysterious compilation errors.

Running this command from the project root:

sbt "project core" compile

Instead of:

cd core && sbt compile

Made all the difference. The first approach maintains the multi-module context, while the second creates an isolated project that doesn't inherit the parent configuration.

Problem 2: Version-Specific Code

Once I understood the version mismatch, I had to fix the prerequisite code that was using Scala 2.13 features. I examined the problematic file:

cat core/src/main/scala/org/cscie88c/core/prereq/Prerequisite.scala

The code was using Using.resource, which doesn't exist in Scala 2.12. I temporarily rewrote it with try-finally blocks for 2.12 compatibility, only to later discover I should have been using 2.13 all along. This taught me an important lesson about understanding the execution context before making fixes.

Problem 3: Test Compilation Dependencies

Even after fixing the main source files, I encountered test compilation failures:

sbt "project core" "testOnly org.cscie88c.core.week4.TrigUtilsTest"

The tests were looking for methods in prerequisite classes that hadn't been implemented. Rather than commenting out tests (my first instinct), I chose to implement the missing methods properly:

def getFilteredTransactions(transactions: List[CustomerTransaction]): List[CustomerTransaction] = {
  transactions.filter(_.transactionAmount > 100)
}

def getAverageTransactionAmountByMonthAndYear(
  transactions: List[CustomerTransaction]
): Map[(String, String), Double] = {
  val grouped = transactions.groupBy { txn =>
    val dateParts = txn.transactionDate.split("-")
    (dateParts(1), dateParts(2))
  }
  grouped.view.mapValues { transactionsInGroup =>
    val sum = transactionsInGroup.map(_.transactionAmount).sum
    sum / transactionsInGroup.size
  }.toMap
}

The Solution Journey

Once I understood the problems, the solutions became clear:

  1. Always run SBT from the project root for multi-module projects, using sbt "project moduleName" command syntax
  2. Verify the actual runtime version rather than assuming based on configuration files
  3. Complete prerequisite implementations rather than bypassing failing tests

The systematic approach I adopted was to:

  • First, ensure compilation worked from the correct context
  • Second, implement all missing prerequisite methods
  • Third, proceed with the actual assignment implementation
  • Finally, verify everything with comprehensive testing

Verification: Making Sure Everything Works

After implementing all solutions, I ran a comprehensive test to ensure nothing was broken:

sbt "project core" "testOnly org.cscie88c.core.week4.*"

The output was finally what I wanted to see:

[info] Run completed in 501 milliseconds.
[info] Total number of tests run: 26
[info] Suites: completed 3, aborted 0
[info] Tests: succeeded 26, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.

Reflection: Lessons Learned

This debugging journey taught me several valuable lessons about working with multi-module Scala projects:

  1. Context matters more than configuration - Where I run commands from can completely change their behavior
  2. Version mismatches hide in plain sight - The same code can behave differently depending on which Scala version is actually being used
  3. Complete solutions beat partial workarounds - Implementing missing methods properly was better than commenting out tests
  4. Systematic investigation beats random fixes - Using CLI tools to understand the actual state prevented me from making incorrect assumptions

The experience reinforced my belief that debugging is as much about understanding systems as it is about fixing code. Each error message was a clue leading to deeper understanding of how build tools, version management, and module systems interact in complex projects.

CLI Command Reference for Debugging

Here's my toolkit of commands that proved invaluable during this debugging session:

# Check Scala version configuration
grep -i "scalaVersion" build.sbt
sbt "show scalaVersion"
sbt "show core/scalaVersion"

# Examine file contents
cat [filepath]
head -n [lines] [filepath]

# Navigate and list directory structures
ls -la [directory]
ls -lah [filepath]

# Git operations for tracking changes
git status
git diff --stat
git add [files]
git commit -m "message"
git push origin [branch]

# SBT compilation and testing
sbt compile
sbt "project [module]" compile
sbt "project [module]" "testOnly [test.class.pattern]"
sbt "project [module]" "testOnly [package].*"

# Check for specific patterns in files
grep -n "pattern" [filepath]

# Create or modify files with heredocs
cat > [filepath] << 'EOF'
[content]
EOF

# Package homework for submission
sbt zipHomework

Each command served a specific purpose in my investigation, from understanding configuration to verifying implementations. The combination of these tools allowed me to systematically identify and resolve each issue without resorting to guesswork.


This debugging experience reminded me that software development is often more about problem-solving and investigation than writing new code. The ability to systematically diagnose issues, understand complex system interactions, and implement targeted fixes is what transforms frustrating problems into learning opportunities.


If you enjoyed this article, you can also find it published on LinkedIn and Medium.